/** -----------------------------------------------------------------------

Copyright 2014 Hiroyuki Tominaga

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

------------------------------------------------------------------------ */
// ------------------------------------------------------------------------------------
//   REVISION LOG ENTRY
//
//	File name  : https://js-extension-afx.googlecode.com/svn/trunk/jsafx.js
// 	Revision By  : (C) Copyright 2010 Hiroyuki Tominaga, All Rights Reserved.
//
//		Revised on: 2010-11-25 09:37:53
//		Summary   : required jquery.js	(1.4.?
//
// ------------------------------------------------------------------------------------

if (!String.prototype.byteLength)
{
	String.prototype.hashCode = function hashCode() {
		var str = this, len = str.length, h = 0;
		for (var i = 0; i < len; i++)
			h = ((h << 5) - h) + str.charCodeAt(i);
		return h;
	};

	// [2010-12-09 09:16:54]
	String.prototype.startsWith = function(str) {
		return (typeof(str) === "string") && ( this.indexOf(str) === 0 );
	};
	// [2010-12-09 09:16:54]
	String.prototype.endsWith = function(str) {
		return (typeof(str) === "string") &&
				( this.lastIndexOf(str) === (this.length - str.length) );
	};

	String.prototype.byteLength = function () {
		var str = this, sz = 0;
		for (var i = 0; i < str.length; i++) {
			var c = str.charCodeAt(i);
			//var c = str[i];
			// windows-31j: 0x0 ～ 0x80, 0xa0 , 0xa1 ～ 0xdf , 0xfd ～ 0xff
			// Unicode : 0x0 ～ 0x80, 0xF8F0, 0xFF61 ～ 0xFF9F, 0xF8F1 ～ 0xF8F3
			// byteorder mark: 0xFEFF none count? SeeTo: ECMA script 262
			if (
				(c >= 0x0 && c < 0x81) || (c == 0xF8F0) || (c >= 0xFF61 && c < 0xFFA0) || (c >= 0xF8F1 && c < 0xF8F4)
			)    { sz += 1; }
			else { sz += 2; }
		}
		return sz;
	};

	String.prototype.formatLegend = function (/*...*/)
	{
		var arglen = arguments.length;
		if (arglen === 0)
			return this;
		var fmt = this;
		for (
			var index = 0; index < arglen;
		) {
			var value = arguments[ index ];
			if(value && typeof value !== "string")
				value = value.toString(); // convert to string...

			var exp = "{" + index++ + "}";
			if (fmt.indexOf(exp) !== -1)
				fmt = fmt.split(exp).join(value);
		}
		return fmt;
	};

	var _N_FMT_EXP = /(-?\d+)(\d{3})/;
	String.prototype.formatNumber = function ()
	{
		var str = this;
		var dot = str.indexOf(".");		// 桁数(小数点の位置)
		var is_float = dot >= 0;
		if( !is_float )
			dot = str.length;			// 小数点がない場合

		var number = str.substring(0, dot);			// 整数値
		//var re = /(-?\d+)(\d{3})/;
		if(!number)		// means empty string. like ".234..".
			number = "0";
		else			// none empty string.
		{
			while(_N_FMT_EXP.test(number)) {
				number = number.replace(_N_FMT_EXP, "$1,$2");
			}
		}

		return (is_float)? number + str.substring(dot, str.length):	// 小数値
						 	number;
	};
	//
	// [2010-12-14 12:15:20] more fast formatNumber. (?
	//
	// formatNx == format number extend
	//
	String.prototype.formatNx = function ()
	{
		var dot = this.indexOf(".");
		var is_float = dot >= 0;
		if( !is_float )
			dot = this.length;

		var number = this.substring(0, dot), ret = "";
		if(!number)	// means empty string. like ".234..".
			ret = "0";
		else       // none empty string.
		{
			if(number.length < 4)
				ret = number;
			else {
				var i = number.length % 3, l = number.length - 3, x = i;
				if(i)
					ret += number.substr(0, i) + ",";
				for(
					; x < l; x += 3
				) ret += number.substr(x, 3) + ",";

				ret += number.substr(x, 3);
			}
		}
		return (is_float)? ret + this.substring(dot, this.length): ret;
	};

	// TODO: uncompleted!
	String.twentyfourTimeString = function(num, b_full)
	{
		if(typeof(num) === "undefined")
			return "NaN";

		var n = parseInt(num); // cut floating value;
		if (isNaN(n))
			throw new Error("cannot convert: " + n);

		var sec = n % 60;
		var min = Math.floor(n / 60) % 60; // need  % 60
		var hour = Math.floor(n / 3600);
		sec < 10 && (sec = "0" + sec), min < 10 && (min = "0" + min), hour < 10 && (hour = "0" + hour);

		return hour + ":" + min  + ":" +  sec;
	};

	// [2010-11-23 03:39:39] added.
	//	@param len :the wrap length.
	//	@param line_suffix : lines suffix. like "\n", "\\\n"...
	//
	String.prototype.wrap = function (len, line_suffix)
	{
		if(typeof(len) !== "number")
			throw new Error("invalid parameter, arg[0] is :" + len);
		!line_suffix && (line_suffix = "\n");

		var str = this;
		var lines = new Array( Math.round(str.length / len) );
		for (var i = 0, x = 0; i < str.length; i += len) {
			lines[x++] = ( str.substring(i, i + len) );
		}
		return lines.join(line_suffix);
	};

	function _html_replacer(ch) {
		switch (ch) {
			case "<": return "&lt;";
			case ">": return "&gt;";
			case "&": return "&amp;";
			case "'": return "&apos;";
			case '"': return "&quot;";
		}
		return "?";
	};
	function _entity_replacer(ch) {
		switch (ch) {
			case "&lt;": return "<";
			case "&gt;": return ">";
			case "&amp;": return "&";
			case "&apos;": return "'";
			case '&quot;': return "\"";
		}
		return "?";
	};
	// [2010-11-26 07:19:42] added.
	String.prototype.escapeHTML = function() {
		return this.length? this.replace(/[<>&"']/g, _html_replacer): this;
	};
	// [2014-06-22 23:32:13] added.
	String.prototype.unescapeHTML = function() {
		return this.length? this.replace(/(&[a-z]+;)/g, _entity_replacer): this;
	};

	//
	// [2010-12-12 12:23:18] added.
	//
	String.prototype.toUniSequence = function() {
		return escape(this).replace(/%/g, "\\");
	};

	// [2014-06-17 13:51:07] implement --------------------------------
	String.prototype.pretag = function (/*...*/) {
		return "<pre>" + this + "</pre>";
	};
	/**
	 * return <tag class="clazz">this</tag>
	 */
	String.prototype.encloseHTML  = function (tag, clazz) {
		return "<" + tag + " class='" + clazz + "'>" + this + "</" + tag + ">";
	};
	String.prototype.findNewLine = function (startIndex, backward) {
		var method = backward? "lastIndexOf": "indexOf";
		return this[method]("\n", startIndex);
	};
	String.prototype.countChar = function (ch, start, last_idx) {
		var count = 0;
		last_idx === undefined && (last_idx = this.length);
		while (last_idx-- > start) {
			this[last_idx] === ch && count++;
		}
		return count;
	};
	// rename "delete" to "remove".
	String.prototype.remove = function (start, length) {
		length === undefined && (length = 1), start < 0 && (start = 0);
		return this.substring(0, start) + this.substring(start + length);
	};
	// [2014-06-17 13:51:07] implement --------------------------------

	// 2014-06-28 20:48:46
	String.prototype.atod = function(radix) {
	    !radix && (radix = 10);
	    var n = parseInt(this, 10);
	    return isNaN(n)? 0: n;
	};

};	// if (!String.prototype.byteLength)


//
// 2014-06-19 22:13:32 implement.
//
if ( !(window || self)["trace"] ) {
	//var regex_fname = /function ([\w]+)\s*?\(/g;
	function trace(msg, callback) {
		var caller = trace.caller;
		(
			!caller && (caller = "[anonymouse]")
		) || (
			caller = /function ([\w]+)\s*?\(/g.exec(caller),
			!caller && (caller = "[anonymouse]") || (
                caller = caller[1]
            )
		);
		msg = caller + ", message :" + msg;
        if (typeof callback !== "function")
            console.log(msg);
        else
		    callback(msg);
	}
}

//
// 2014-07-18 12:28:30 implement.
//
// Object as a namespace, not create a function object.
function createNs(ns, ns_object, factory_method)
{
    if (typeof ns !== "string" || ns.length === 0)
        return;

    if (!ns_object) ns_object = window;
    if (typeof factory_method !== "function")
    	factory_method = function() { return {}; };

    var done = false, chk_root = true;
    var name;
    while(!done) {
        var idx = ns.indexOf(".");
        done = (idx === -1);
        if (done)
            idx = ns.length;
        name = ns.substring(0, idx);
        if (!done)
            ns = ns.substring(idx + 1);

        if (ns_object[name] === undefined)
            ns_object[name] = factory_method();
        else
            throw new Error("cannot create name space '" + name + "'");

        ns_object = ns_object[name];
    }
}

// ------------------------ string utilities.
// 2014-07-01 07:49:53
function unHyphenate(string) {
	if (typeof string === "string") {
		if (/(?:\-)(\w{1})/g.test(string)) {
			return string.replace(/(?:\-)(\w{1})/g, function($0, $1) {
				//console.log("$0=%s, $1=%s", $0, $1);
				return $1.toUpperCase();
			});
		}
	}
	return string;
}
/*
// 使用方法 :
utf8_to_b64('? a la mode'); // "4pyTIMOgIGxhIG1vZGU="
b64_to_utf8('4pyTIMOgIGxhIG1vZGU='); // "? a la mode"
*/
function utf8Tob64(str) {
	return window.btoa( unescape(encodeURIComponent( str )) );
}
function b64ToUtf8(str) {
	return decodeURIComponent( escape(window.atob( str )) );
}
/*
from :https://developer.mozilla.org/ja/docs/Web/API/window.scrollY
クロスブラウザ互換性の為、window.scrollY ではなく window.pageYOffset を使用します.
さらに、古いバージョンの Internet Explorer (IE9 以前) は何れもサポートしていないため,
チェックの上、無ければ他の非標準の方法で回避する必要があります。以下に互換性を確保した例を示します。
*/
// var w = window.innerWidth;
// var h = window.innerHeight;
function getActualScrollPosition() {
var x = (window.pageXOffset !== undefined) ? window.pageXOffset:
		(document.documentElement || document.body.parentNode || document.body).scrollLeft,
	y = (window.pageYOffset !== undefined) ? window.pageYOffset:
		(document.documentElement || document.body.parentNode || document.body).scrollTop;
	return { scrollX: x, scrollY: y };
}


//
// Short cut of document.getElementById
//
var $dom = function $dom(id) {
	return document.getElementById(id);
};
function $tags(tag) {
	return document.getElementsByTagName(tag);
}
function $selectors(selector) {
	return document.querySelectorAll(selector);
}
// 2014-06-27 06:58:52 modify.
function createE(tag, attrs) {
	var node = document.createElement(tag), attr;
	for (attr in attrs) {
		if (attrs.hasOwnProperty(attr)) {
			node.setAttribute(attr, attrs[attr]);
		}
	}
	return node;
}


// ---------------------------------------------------------------------------------------------- [test]
var WPerformance = window.performance || { now: function(){return -1;} };
//
//
//
if ("LOG_PERFORMANCE" in window) {
document.onreadystatechange = function(e) {
	console.log("jsafx.js#639: " + document.readyState + ", time=" + WPerformance.now());
};
}

//
//
//
function observeEvents(element, regex, remove) {
	function _obe_(ev) {
		console.log(ev.type);
	};
	for (var i in element) {
		try {
			if (i.startsWith("on")) {
				if ( (regex instanceof RegExp) && regex.test(i) )
					continue;
				var fn = element[i];
				if (!remove && fn === null) {
					element[i] = _obe_;
				} else {
					fn && fn.toString().indexOf("_obe_") > 0 && (element[i] = null);
				}
			}
		} catch (e) {
			console.log(e.message);
		}
	}
}
// jQuery を利用した event observe, namespace を使用.
function observeEvents$(element, callback, regex, remove, capturephaze) {
	function _obe_(ev) {
		console.log(ev.type);
	};
	!$.isFunction(callback) && (callback = _obe_);
	capturephaze === undefined && (capturephaze = false);
	var method = (remove? "remove": "add") + "EventListener";
	for (var prop in element) {
		try {
			if (prop.startsWith("on")) {
				if ( (regex instanceof RegExp) && regex.test(prop) )
					continue;

				element[method](prop.substring(2), callback, capturephaze);
			}
		} catch (e) {
			console.log(e.message);
		}
	}
}

// ---------------------------------------------------------------------------------------------- [test]

/** 2014-06-09 19:21:49
 * map を作成して返す.
 * query がないときは空の map が返される.
 * null を返すことはない.
 */
function parseCurrentQuery() {
	var q = location.search;
	var map = {}, pairs;
	if (q) {
		pairs = q.substring(1).split("&");
		for (var i in pairs) {
			var pair = pairs[i].split("=");
			map[pair[0]] = pair[1];
		}
	}
	return map;
}

//
// suqential call functions.
//
function callSequentially(arry, arg, clean) {
    for (var i in arry) {
    	if (!arry.hasOwnProperty(i))
    		continue;
		var fn = arry[i];
		if (clean)
			arry[i] = undefined;
		if (typeof fn === "function")
			fn(arg);
    }
}
/** 2014-06-09 19:59:14
 * "rgb(n, n, n)" format の css を "#nnnnnn" に変換.
 */
function rgb2HexColor(rgb_color) {
	var regexp = /rgb\(([\d]{1,3}),? ([\d]{1,3}),? ([\d]{1,3})\);?/g;
	var m = regexp.exec(rgb_color);
	if (m !== null) {
		var r = Number(m[1]).toString(16);
		(r.length === 1) && (r = "0" + r);
		var g = Number(m[2]).toString(16);
		(g.length === 1) && (g = "0" + g);
		var b = Number(m[3]).toString(16);
		(b.length === 1) && (b = "0" + b);
		return "#" + r + g + b;
	}
	return "";
}

function hex2RgbColor(hex_color) {
	var hexexp = /#?([\d\w]{2})([\d\w]{2})([\d\w]{2})/g;
	var m = hexexp.exec(hex_color);
	if (m !== null) {
		var r = Number("0x" + m[1]).toString(10);
		var g = Number("0x" + m[2]).toString(10);
		var b = Number("0x" + m[3]).toString(10);
		return "rgb({0}, {1}, {2})".formatLegend(r, g, b);
	}
	return "";
}


/**
 * for addElementsTo
 * object is: { tag: tag, text: text, attrs: attrs }
 */
function elementSet(tag, text, attrs) {
	return {
		tag: tag, text: text, attrs: attrs
	}
}


if ( !(window || self)["$ls"]  ) {
	(window || self).$ls = function (url, async, defer) {
		var script = createE('script', {
			type: 'text/javascript',
			src: url
		});
		async && (script.async = "true");
		defer && (script.defer = "true");

		var s = $tags('script');//[0];
		s = s[s.length - 1];
		// note: 2014-06-08 15:46:54 script の挿入順序は重要!
		// nextSilbling で後ろに挿入できる.

		// note: 2014-06-08 16:21:34 IE10 では parentNode, nextSilbling
		// は認識されなかった.
		// IE, chrome 両方とも support している property name に変更.
		s.parentElement.insertBefore(script, s.nextElementSibling);
	};
}


function _blm(bind_name, e, fn, ename_arry, /*optional*/useCapture ) {
	if (typeof ename_arry === "string") {
		var separator = /,/.test(ename_arry)? ",": /\|/.test(ename_arry)? "|": " ";
		ename_arry = ename_arry.split(separator);
	}
	useCapture === undefined && (useCapture = false);
	ename_arry.forEach(function (value, index, arry) {
		e[bind_name](value, fn, useCapture);
	});
}
/**
 * @param e          addEventListener を実行する HTML element.
 * @param ename_arry string の場合, separator には "," "|" " "が使える.
 *                   通常は array.
 * @param fn         event を handle する function.
 */
function addListenersMultiple(e, fn, ename_arry, /*optional*/useCapture ) {
	e && _blm("addEventListener", e, fn, ename_arry, useCapture );
}
function removeListenersMultiple(e, fn, ename_arry, /*optional*/useCapture ) {
	e && _blm("removeEventListener", e, fn, ename_arry, useCapture );
}

//
// [2014-07-22 06:25:49] from html5-text-extension.js
//
function add_namespace(tokens, ns, delim) {
    var ns_events = [];
    if (delim === undefined) delim = " ";
    tokens.split(delim).forEach(function(e, i, arry) {
        ns_events.push(e + "." + ns);
    });
    return ns_events.join(delim);
}



//
// shortcut function.
// consume event.
// 2014-06-23 14:49:58 add stopImmediatePropagation.
//
function consumeE(e, immediate) {
	var method = immediate? "stopImmediatePropagation": "stopPropagation";
	e[method]();
	e.preventDefault();
}


// 2014-06-08 14:16:38 これはうまくいかなかった...
//loadScript("http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/jquery-ui.min.js");
//loadScript("http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js");

//from :http://www.html5rocks.com/ja/tutorials/file/dndfiles/
// Check for the various File API support.
if (window.File && window.FileReader && window.FileList && window.Blob) {
	console.log('All the File APIs are supported.');
} else {
	console.log('The File APIs are not fully supported in this browser.');
}


/**
 * jQuery extension
 */
if ( (window || self).jQuery ) {

	(function ($) {
    /* Strict mode for this plugin */
    "use strict";

	var _DEBUG = 0;
	$.jsafxDebug = function(is) {
		_DEBUG = is;
	};

	// 2014-06-19 20:53:56 from jquery ui.
	(function() {
		var kc = $.keyCode;
	    if (!kc) {
	    	$.keyCode = kc = {
				BACKSPACE: 8,
				COMMA: 188,
				DELETE: 46,
				DOWN: 40,
				END: 35,
				ENTER: 13,
				ESCAPE: 27,
				HOME: 36,
				LEFT: 37,
				NUMPAD_ADD: 107,
				NUMPAD_DECIMAL: 110,
				NUMPAD_DIVIDE: 111,
				NUMPAD_ENTER: 108,
				NUMPAD_MULTIPLY: 106,
				NUMPAD_SUBTRACT: 109,
				PAGE_DOWN: 34,
				PAGE_UP: 33,
				PERIOD: 190,
				RIGHT: 39,
				SPACE: 32,
				TAB: 9,
				UP: 38
			};
	    }
		for (var alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ", i = 0; i < alpha.length; i++)
			kc[alpha[i]] = alpha[i].charCodeAt(0);
	})();


	//(window || self).$ = $;
	// 2014-06-18 14:21:22 implement.
	$.generateId = function generateId(length)
	{
		var id = "_uid_";
		var id_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";
		var l = id_chars.length;
		while (length--) {
			id += id_chars[ Math.floor(Math.random() * l) ];
		}
		return id;
		//"closure_uid_" + Math.floor(Math.random() * 2147483648).toString(36);
	};
	// ---------------------------------- getActualScrollPosition
	$.gasp = function gasp(jqo) {
		var x = (window.pageXOffset !== undefined) ? window.pageXOffset:
				(document.documentElement || document.body.parentNode || document.body).scrollLeft,
			y = (window.pageYOffset !== undefined) ? window.pageYOffset:
				(document.documentElement || document.body.parentNode || document.body).scrollTop;
		return { scrollX: x, scrollY: y };
	};

	// ---------------------------------- computeOfCenter
	$.computeOfCenter = function coc(jqo) {
		var win = $(window);
		!(jqo instanceof jQuery) && (jqo = $(jqo));
		return {
			position: "absolute",
			top: Math.max(0, ((win.height() - jqo.outerHeight()) / 2) + win.scrollTop()) + "px",
			left: Math.max(0, ((win.width() - jqo.outerWidth()) / 2) + win.scrollLeft()) + "px"
		};
	};
	// ---------------------------------- confineToWindow
	// for context menu.
	$.confineToWindow = function ctw(jqo, event, display/* optional */) {
		var win = $(window);
        var wp = $.gasp();
        var top = event.clientY, left = event.clientX;

		!(jqo instanceof jQuery) && (jqo = $(jqo));
        var joh = jqo.outerHeight(), jow = jqo.outerWidth();
        if (top > window.innerHeight - joh)
            top = window.innerHeight - joh;
        if (left > window.innerWidth - jow)
            left = window.innerWidth - jow;

		// 最後に window scroll position を加算.
        top += wp.scrollY, left += wp.scrollX;
		return {
			position: "absolute",
            top: (top < 0? 0: top) + "px", left: (left < 0? 0: left) + "px",
            display: display? display: "block"
		};
	};

	/* helper functions for flashColor */
	// extract hex color.
	function ehc(color) {
		var m = /#?([\d\w]{2})([\d\w]{2})([\d\w]{2})/g.exec(color);
		if (m !== null) {
			return [ Number("0x" + m[1]), Number("0x" + m[2]), Number("0x" + m[3]) ];
		} else {
			throw new Error("cannot extract color.");
		}
	}
	function is_reach(rgb, rgb_ed, steps) {
		var hit = 0;
		for (var i = 0; i < 3; i++) {
			var negative = steps[i] < 0;
			if (negative && rgb[i] <= rgb_ed[i]) {
				hit++;
			} else {
				if (rgb[i] >= rgb_ed[i])
					hit++;
			}
		}
		return hit === 3;
	}


    var jq_fn = $.fn;
	// jquery ui に依存したくないときに利用.
	// jquery の animte は css color property の書き換えが出来ない.
	/**
	 * @param flashColor supported "#000000" or "rgb(n, n, n)".
	 *                   NOTE: rgba is not supported.
	 */
    jq_fn.flashColor = function (flashColor, callback)
    {
		var tid, finishColor, rgb, rgb_ed, steps;
		var style;
		/**
		 * function _flash (void)
		 */
		// 2014-06-24 15:28:04 note: strict mode では if (!style._flashLock) に nest すると error になる.
		function _flash2()
		{
			for (var i = 0; i < 3; i++) {
				var n = rgb[i];
				n += steps[i];
				n <= 0 && (n = 0, 1) || n >= 255 && (n = 255);
				rgb[i] = n >> 0;
			}
		    if ( is_reach(rgb, rgb_ed, steps) ) {
		    	style.backgroundColor = finishColor;
		    	delete style._flashLock;
		    	clearInterval(tid);
		    	typeof callback === "function" && callback();
		        return;
		    }

		    style.backgroundColor = "rgb(" + rgb[0] + ", " + rgb[1] + ", " + rgb[2] + ")";
		};

        style = this[0].style;
		if (!style._flashLock) {

			style._flashLock = true;

			flashColor = flashColor.trim();
			if (flashColor.indexOf("rgb") === 0)
				flashColor = rgb2HexColor(flashColor);

	        finishColor = this.css("background-color");
			if (finishColor.indexOf("rgb") === 0)
				finishColor = rgb2HexColor(finishColor);

			/* start hex color */
			rgb = ehc(flashColor);
			/* end hex color */
			rgb_ed = ehc(finishColor);
			steps = []; {
				var step;
				for (var i = 0; i < 3; i++) {
					if (rgb[i] < rgb_ed[i]) // plus
						step = (rgb_ed[i] - rgb[i]) / 25;
					else if (rgb[i] > rgb_ed[i]) // minus
						step = -(rgb[i] / rgb_ed[i]) / 25;
					else
						step = 0;
					steps.push(step);
				}
			}

			style.backgroundColor = flashColor;
			tid = setInterval(_flash2, 10);
		}
        return this;
    };

    var _al = "addEventListener", _rl = "removeEventListener";
    /**
     * @param callback Specify callback function when you need debug.
     * この function は, 簡易 drag move を support します.
     * 高機能な drag move が必要な場合は jQuery UI などを使用のこと.
     */
    jq_fn.dragWindow = function (capturePhaze, callback)
    {
        var _x = 0, _y = 0, _e, _id;
        //console.log(Manager);
        this.release = function() {
            this.reset();
            _removeListener();
            _e && _e[_rl]('mousedown', _mdown, capturePhaze);
            Manager.remove(_e);
            _e = null;
            _id = "";
            return this;
        };
        this.reset = function() {
            _x = 0, _y = 0;
        };
        this.setTarget = function(element) {
            this.release();
            _e = (element && typeof element !== "string")? element: null;
            try {
                _id = (element && typeof element === "string")? element: element.id;
            } catch (e) {
                console.log(e), _id = "";
            }
            _addListener(), Manager.add(_e);
            return this;
        };

        function logEvent(e) {
            if (!callback) return;
            callback(e);
        }

        /* If define as a prototype apparently a reference function seems to fail */
        /**
         * !!need implemented as closure function
         */
        function _addListener() {
            var target = _e;
            // lazy initiaize?.
            if (target === null) {
                _e = target = document.getElementById(_id);
            }
            target[_al]('mousedown', _mdown, capturePhaze);
        };
        function _removeListener() {
            if (_e) {
            _e[_rl]('mousemove', _dragmove, capturePhaze);
            _e[_rl]('mouseup', _mup, capturePhaze);
            }
        };

        function _mdown(e) {
        	if (!e) e = window.event;
            logEvent(e);
            var target = e.target;
            if (target.id === _id) {
	            if (e.offsetX === undefined) { // firefox 対応.
	                var off = $(target).offset();
	                _x = e.pageX - off.left, _y = e.pageY - off.top;
	                //_x = e.layerX, _y = e.layerY;
	            } else {
	                _x = e.offsetX, _y = e.offsetY;
	            }
                target[_al]('mousemove', _dragmove, capturePhaze);
                target[_al]('mouseup', _mup, capturePhaze);
                Manager.mostTop(target);
                e.preventDefault();
            }
        };
        function _dragmove(e) {
            logEvent(e);
        	if (!e) e = window.event;
            var ss = _e.style, wsp = $.gasp();
            ss.position = 'absolute';
            ss.top = e.clientY - _y + wsp.scrollY + 'px';
            ss.left = e.clientX - _x + wsp.scrollX + 'px';
            e.preventDefault();
        };
        function _mup(e) {
        	if (!e) e = window.event;
            logEvent(e);
            _removeListener(), e.preventDefault();
        };

        capturePhaze === undefined && (capturePhaze = false);
        return this.setTarget(this[0]);
    }; // ---------------------------------- dragWindow

    /* singleton */
    var Manager = {
        lock: false,
        elements: [],
        add: function(element) {
            if (element && !this.lock)
                this.elements.push(element);
        },
        remove: function(element) {
            if (element && !this.lock) {
                this.lock = true;
                var arry = this.elements, found;
                for (var i in arry) {
                    if (arry[i] === element) {
                        arry[i] = undefined, found = true;
                        break; // "," での記述は NG
                    }
                }
                if (found) {
                    var new_arry = new Array(arry.length - 1), idx = 0;
                    for (var i in arry) {
                        arry[i] && (new_arry[idx++] = arry[i]);
                    }
                    this.elements = new_arry;
                }
                this.lock = false;
            }
        },
        mostTop: function(element) {
            if (element && !this.lock) {
                this.lock = true;
                var arry = this.elements, max = 0, zIndex = 0;
                for (var i in arry) {
                    var e = arry[i];
                    if (e !== element) {
                        zIndex = parseInt(e.style.zIndex);
                        isNaN(zIndex) && (zIndex = 0), zIndex > max && (max = zIndex);
                    }
                }
                var zIndex2 = parseInt(element.style.zIndex);
                isNaN(zIndex2) && (zIndex2 = 0), zIndex2 <= max && (element.style.zIndex = max + 1);
                this.lock = false;
            }
        }
    }; // ------------------------- Manager

	/**
	 * @param css_class css animation と関連図けられた css class name(ひとつのみ.
	 * @param callback  css animation 終了時に call される function.
	 * note: available only single object.
	 */
    jq_fn.animateCss = function(css_class, callback) {
    	var _this = this;
    	function _animte_done(e) {
    		_DEBUG && console.log(e.type);
			switch(e.type) { /* no namespace. */
				case "animationend":
				case "MSAnimationEnd":
				case "webkitAnimationEnd": {
					_this.unbind( e.type + ".animate-css" );
					$.isFunction(callback) && callback();
					_DEBUG && console.log("_animte_done");
					break;
				}
			}
    	}
    	// use namespace ".animate-css"
	    return this.addClass( css_class ).on(
		    "animationend.animate-css MSAnimationEnd.animate-css webkitAnimationEnd.animate-css",
		    _animte_done
	    );
	}; // ---------------------------------- animateCss

	// Also possible to specify multiple class names separated by spaces.
	/**
	 * @param remove_class remove css class names.
	 * @param add_class    add css class names.
	 * @param css          apply css properties, must plain object.(optional
	 */
    jq_fn.swapClass = function(remove_class, add_class, css/* optional */) {
		this.removeClass(remove_class).addClass(add_class);
		return $.isPlainObject(css)? this.css(css): this;
    }; // ---------------------------------- swapClass

	/**
	 * @param always Specify true when you need to place in the center at all times.
	 * note: available only single object.
	 */
    jq_fn.center = function(always) {
    	var _this = this;
    	function __w_onresize() {
			_this.css( $.computeOfCenter(_this) );
    	}
	    if (always) {
	    	// call side で $(window).unbind("resize.center") を実行してもらうしかない.
	    	$(window).on("resize.center", __w_onresize);
    	}
	    return this.css( $.computeOfCenter(this) );
	}; // ---------------------------------- center

	var POPUP_FADE_TIME = 150;
    jq_fn.popupWindow = function (fn) {
    	!fn && (fn = $.noop);
	    return this.center(true).fadeIn(POPUP_FADE_TIME, fn);
	}; // ---------------------------------- popupWindow

	/**
	 * z-index: 1000 の popup window style にします.
	 * position: absolute
	 * fadeIn, fadeOut と使うと効果的です.
	 */
	jq_fn.toPopupWindow = function () {
		return this.css( {
			"position": "absolute",
			"top": "auto",
			"left": "auto",
			"bottom": "auto",
			"right": "auto",
			"z-index": "1000",
			"display": "none"
		});
	}; // ---------------------------------- toPopupWindow

	// ----------------------------------- popup window implementation. -----------------------------------
	var _popup_showing;
	var POPUP_STAY_TIME = 3000;
	//
	// [2014-06-18 13:04:08] NOTE:
	//	何年前だろうか, たしか javascript で if (null) は true だったはずなんだが
	//	現在は false を返すようになっている.
	//
	var popup_install;
	var default_popup_css = {
		"background-color": "#F5FADC",
		"border-radius": "8px",
		"padding": "4px 8px",
		"-webkit-box-shadow": "2px 2px 8px silver",
		"box-shadow": "2px 2px 8px silver"
	};
	/*
	$("<div id='testX'>").toPopupWindow().appendTo("body").css({
		"background-color": "#F5FADC",
		"border-radius": "8px",
		"padding": "4px 8px",
		"-webkit-box-shadow": "2px 2px 8px silver",
		"box-shadow": "2px 2px 8px silver"
	});
	$("#testX").html("<pre>this is a test!!</pre>").popupWindow();
	$("#testX").fadeOut(1000);
	*/
	/**
	 * 簡易 popup window.
	 * 自動的に html body に popup 専用の div tag を追加します.
	 * @see toPopupWindow()
	 * @see appendTo(<selector>)
	 * @see popupWindow(callback)
	 */
	$.popupInfomation = function (html, stay)
	{
		if (_popup_showing) {
			console.log("popupInfomation buzy...");
			return;
		}

		_popup_showing = true;
		!stay && (stay = POPUP_STAY_TIME);

		var jqo;
		!popup_install && (
			jqo = $("<div id='_unique_popup_id_'>").toPopupWindow().appendTo("body").css(default_popup_css),
			popup_install = true
		) || (
			jqo = $("#_unique_popup_id_")
		);

	    jqo.html(html).popupWindow(
	    	function() {
	        	jqo.delay(stay).fadeOut(POPUP_FADE_TIME, function() {
	        	_popup_showing = false;
	        })
	    });
	}; // ---------------------------------- popupInfomation
	// ----------------------------------- popup window implementation. -----------------------------------

	// run pretty print timer.
	/**
	 * @param selector element selector( "#element", ".element", must be only one.
	 * @param interval timer interval time(ms, default 30ms
	 */
	$.forcePrettyPrint = function(selector, interval)
	{
	    if (PR === undefined) {
			console.log("google pretty print script not loaded.");
			return;
	    }
	    if (selector === undefined || !selector.length) {
			console.log("must be set a selector parameter.");
			return;
	    }
		// 2014-07-01 13:23:58 interval 30ms だと, firefox が応答しない case があった.
		// 100ms で大丈夫だろうと思うが...
	    if (interval === undefined)
	    	interval = 100;

        var jqo = $(selector);
	    var pretty_tid = window.setInterval(function () {
	        if ( /<ol/gi.test(jqo.html()) ) {
	            window.clearInterval(pretty_tid);
	            return;
	        }
	        jqo.removeClass("prettyprinted");
	        PR.prettyPrint();
	    }, interval);
	};

	})( (window || self).jQuery );

} // ------ if (jQuery)



